#include "stdafx.h"
#include "stdio.h"
#include "..\include\imp_typedefine.h"
#include "..\include\imp_timer_wrapper.h"
#include "..\include\imp_io_wrapper.h"
#include <mmsystem.h>

typedef struct _t_imeTimer
{
	Int32				id;
	Int32				msticks;
	Int32				delta;
	ImpDate				date;
	ImpTimer_FuncType	*func;
	void				*inst;
	void *param;
}t_impTimer;

static t_impTimer *g_Timer = 0; //only one timer allowed, for bug-fixing
static Int32 g_timerActive = 0; //flag if the timer is active or stop
static void TimerCallback(void)
{
	ImpTimerInfo info;
	if(g_timerActive == 0)
		return;
	info.delta = g_Timer->delta;
	info.param = g_Timer->param;
	g_Timer->msticks += g_Timer->delta;
	g_Timer->func(&info);
}

#define WAV_BLOCK_NUMBER 16
typedef struct _t_audio_clock
{
	HWAVEOUT		hwo;
	WAVEFORMATEX	wfx;
	WAVEHDR			wdr[WAV_BLOCK_NUMBER];
	Int16			*buffer[WAV_BLOCK_NUMBER]; // sound buffer, fs = 8k, bs = 16bits, ch = 1
}t_audio_clock;

static void CALLBACK waveOutProc(
  HWAVEOUT m_hwo,
  UINT uMsg,
  DWORD dwInstance,
  DWORD dwParam1,
  DWORD dwParam2
)
{
	t_audio_clock *audio = (t_audio_clock *)dwInstance;
	if(uMsg==WOM_DONE && audio)
	{
		waveOutWrite(audio->hwo, (WAVEHDR *)dwParam1, sizeof(WAVEHDR));
		TimerCallback();
	}
	return;
}

ImpTimer imptimer_open(Int32 millisecond, ImpTimer_FuncType *func, void *param/* = 0*/)
{
	//only one timer allowed, for bug-fixing
	if(!g_Timer)
	{
		HRESULT hr;
		MMRESULT mhr;
		Int32 n = 8 * millisecond; // 8kfps * ms, 16bits/sample
		t_audio_clock *audio = (t_audio_clock *)IMP_Malloc( sizeof(t_audio_clock) );
		t_impTimer *timer = (t_impTimer *)IMP_Malloc( sizeof(t_impTimer) );
		IMP_Memset(timer, 0, sizeof(t_impTimer));
		timer->delta = millisecond;
		timer->func = func;
		timer->msticks = 0;
		timer->param = param;

		timer->inst = (void*)audio;

		audio->wfx.wFormatTag = WAVE_FORMAT_PCM;
		audio->wfx.nChannels = 1;
		audio->wfx.nSamplesPerSec = 8000;
		audio->wfx.wBitsPerSample = 16;
		audio->wfx.cbSize = 0;
		audio->wfx.nAvgBytesPerSec = 8000*16/8;
		audio->wfx.nBlockAlign = 1*16/8;
		hr = waveOutOpen(&audio->hwo, WAVE_MAPPER, &audio->wfx, (DWORD)waveOutProc, (DWORD)audio, CALLBACK_FUNCTION);
		IMP_Assert(hr==0, "imptimer_open fail\n");

		for(Int32 i=0; i<WAV_BLOCK_NUMBER; i++)
		{
			audio->buffer[i] = (Int16*)IMP_Malloc( n*2 );
			IMP_Memset(audio->buffer[i], 0, n*2);

			audio->wdr[i].dwBufferLength	= n*2;
			audio->wdr[i].dwBytesRecorded	= 0;
			audio->wdr[i].dwFlags			= 0;
			audio->wdr[i].dwLoops			= 1;
			audio->wdr[i].dwUser			= 0;
			audio->wdr[i].lpNext			= 0;
			audio->wdr[i].lpData			= (Int8*)audio->buffer[i];

			mhr = waveOutPrepareHeader(audio->hwo, &audio->wdr[i], sizeof(audio->wdr[i]));
			IMP_Assert(mhr==0, "imptimer_open fail\n");

		}
		g_Timer = timer;

		for(Int32 i=0; i<WAV_BLOCK_NUMBER; i++)
			waveOutWrite(audio->hwo, &audio->wdr[i], sizeof(audio->wdr[i]));
	}
	g_Timer->func = func;
	g_Timer->msticks = 0;
	g_Timer->param = param;
	g_timerActive = 1;
	return (ImpTimer)g_Timer;
}

ImpBool imptimer_close(ImpTimer inst)
{
	g_timerActive = 0;
	//never close the timer, for bug-fixing
	/*
	t_impTimer *timer = (t_impTimer*)inst;
	if(timer->inst){
		t_audio_clock *audio = (t_audio_clock*)timer->inst;
		for(Int32 i=0; i<WAV_BLOCK_NUMBER; i++)
		{
			waveOutUnprepareHeader(audio->hwo, &audio->wdr[i], sizeof(audio->wdr[i]));
			IMP_Free( audio->buffer[i] );
		}
		waveOutClose(audio->hwo);
		IMP_Free( audio );
	}
	IMP_Free( timer );
	*/
	return IMP_True;
}

Int32 imptimer_get_msticks(ImpTimer inst)
{
	t_impTimer *timer = (t_impTimer*)inst;
	if(inst)
		return timer->msticks;

	return GetTickCount();
}

ImpBool imptimer_set_msticks(ImpTimer inst, Int32 ms)
{
	t_impTimer *timer = (t_impTimer*)inst;
	if(inst)
		timer->msticks = ms;
	return IMP_True;
}

#define MAX_APP_TIMER (10)
static t_impTimer *g_Timer_app_store[MAX_APP_TIMER];

static void TimerSave(t_impTimer *timer)
{
    for (Int32 i = 0; i < MAX_APP_TIMER; i++)
    {
        if (g_Timer_app_store[i] == 0)
        {
            g_Timer_app_store[i] = timer;
			break;
        }
    }
}

static void TimerDelete(t_impTimer *timer)
{
    for (Int32 i = 0; i < MAX_APP_TIMER; i++)
    {
        if (g_Timer_app_store[i] == timer)
        {
            g_Timer_app_store[i] = 0;
			break;
        }
    }
}

static t_impTimer *TimerFind(Int32 id)
{
    for (Int32 i = 0; i < MAX_APP_TIMER; i++)
    {
        if (g_Timer_app_store[i]->id == id)
        {
            return g_Timer_app_store[i];
        }
    }

    IMP_Assert(0, "TimerFind(0)");
    return 0;
}

static void TimerCallbackApp(HWND hWnd,UINT nMsg,UINT nTimerid,DWORD dwTime)
{
	ImpTimerInfo info;

    t_impTimer* timer = TimerFind(nTimerid);
	
	if(timer == 0)
		return;

	info.delta = timer->delta;
	info.param = timer->param;
	timer->msticks += timer->delta;
	timer->func(&info);
}

ImpTimer imptimer_app_open(Int32 millisecond, ImpTimer_FuncType *func, void *param)
{
	t_impTimer *timer = (t_impTimer *)IMP_Malloc( sizeof(t_impTimer) );
	IMP_Memset(timer, 0, sizeof(t_impTimer));
	timer->delta = millisecond;
	timer->func = func;
	timer->msticks = 0;
	timer->param = param;
	timer->id = (Int32)SetTimer(0, 100, millisecond, (TIMERPROC)TimerCallbackApp);

    TimerSave(timer);
    
	return (ImpTimer)timer;
}

ImpBool imptimer_app_close(ImpTimer inst)
{
	t_impTimer *timer = (t_impTimer*)inst;
	if(timer->id)
		KillTimer(0, timer->id);
	
	TimerDelete(timer);
	IMP_Free( inst );

	return IMP_True;
}

ImpBool imptimer_get_time( ImpDate *current )
{
	SYSTEMTIME stCurrent;
	GetLocalTime( &stCurrent );

	current->year = stCurrent.wYear;
	current->month = stCurrent.wMonth;
	current->day = stCurrent.wDay;
	current->hour = stCurrent.wHour;
	current->minute = stCurrent.wMinute;
	current->second = stCurrent.wSecond;
	current->ms = stCurrent.wMilliseconds;

	return IMP_True;
}
